#include "HeroClass.h"

#include <string>
#include <vector>
#include "Camera.h"
#include "StatefulUnit.h"
#include "Level.h"
#include "Physics.h"
#include "Camera.h"
#include "Hollywood.h"
#include "NumberUtility.h"

// In pixels or milliseconds
#define MAX_NORMAL_MOVEMENT 12
#define MAX_ATTACK_MOVEMENT 18
#define ATTACK_DURATION 750
#define MAX_JUMP_SPEED -22
#define MAX_JUMP_COUNTER 7
#define MAX_POUND_SPEED 24
#define MAX_THROW_FORCE 36
#define CROSSHAIR_DISTANCE_TO_PLAYER 150.0f
#define CROSSHAIR_MAX_ANGLE 380.0f
#define CROSSHAIR_MIN_ANGLE 160.0f
#define CROSSHAIR_MOVEMENT_STEP 5

Hero::Hero(const Vector2df p, const Vector2df v) : StatefulUnit(p,v)
{
    loadSprite("WandorWhole.sprite");

    setSpriteState("Right");
    m_canJump = false;
    m_maxSpeed = Vector2df(MAX_ATTACK_MOVEMENT,MAX_JUMP_SPEED);

    m_thinkTime.init(200);
    m_thinkTime.setRewind(STOP);
    m_thinkTime.start();

    m_direction = 1;
    m_parent = NULL;
    m_child = NULL;
    m_state = hsNORMAL;
    m_crosshair = NULL;
    m_crosshairAngle = 360;
} // ctor

Hero::~Hero()
{

}


//----------------------
// public declarations
//----------------------

void Hero::hit(const SimpleDirection dir, BaseUnit* unit, CRstring parameter)
{
    switch(dir.value)
    {
    case diBOTTOM:
    case diBOTTOMLEFT:
    case diBOTTOMRIGHT:
        // If jumping on a unit check for key press and push player into the air
        if (getLevel()->GetInput()->isA())
        {
            m_velocity.y = -26;
        }
        else
        {
            m_velocity.y = -12;
            m_thinkTime.start(200);
        }
        break;
    case diLEFT:
        switch (unit->getState())
        {
        case usPICKUPABLE:
            if (isCarryingChild())
            {
                loseChild();
                m_velocity.x = 12;
                m_velocity.y = -7;
                accelerate(Vector2df(-1,0),Vector2df(0,0));
                m_thinkTime.start(200); // This timer disables input for a short time
            }
            else
            {
                switch (m_state)
                {
                    case hsDUCKED:
                    case hsATTACKING:
                        break;
                    default:
                        if (not m_child)
                        {
                            m_child = unit;
                            unit->setParent(this);
                        }
                        break;
                }
            }
            break;
        default:
            if (isCarryingChild())
            {
                loseChild();
            }
            // bounce him back a little bit
            m_velocity.x = 12;
            m_velocity.y = -7;
            accelerate(Vector2df(-1,0),Vector2df(0,0));
            m_thinkTime.start(200); // This timer disables input for a short time
            break;
        }
        break;
    case diRIGHT:
        switch (unit->getState())
        {
        case usPICKUPABLE:
            if (isCarryingChild())
            {
                loseChild();
                m_velocity.x = -12;
                m_velocity.y = -7;
                accelerate(Vector2df(1,0),Vector2df(0,0));
                m_thinkTime.start(200);
            }
            else
            {
                switch (m_state)
                {
                    case hsDUCKED:
                    case hsATTACKING:
                        break;
                    default:
                        if (not m_child)
                        {
                            m_child = unit;
                            unit->setParent(this);
                        }
                        break;
                }
            }
            break;
        default:
            if (isCarryingChild())
            {
                loseChild();
            }
            m_velocity.x = -12;
            m_velocity.y = -7;
            accelerate(Vector2df(1,0),Vector2df(0,0));
            m_thinkTime.start(200);
            break;
        }
        break;

    }
    handleEvent("Hit");
} // hit

void Hero::hitMap(MapData* const data, const CollisionObject* const obj)
{
    Vector2df correction = obj->getTotalCorrection();

    if (correction != Vector2df(0,0))
        handleEvent("HitMap");

    // Remove camera tiles
    SDL_Rect rect = CAMERA->getVisibleTileRect();
    data->removeCameraTiles(obj,getPosition(true),Vector2df(rect.x,rect.x + rect.w),Vector2df(rect.y,rect.y + rect.h));

    /// y-collision
    switch (m_state)
    {
    case hsPOUNDING: // pounding
        // break tiles Wandor pounds on
        correction.y = data->breakTiles(obj,1,false,true).y;
        if (correction.y < 0) // is he hitting something hard?
        {
            // end pounding
            m_state = hsNORMAL;
            getLevel()->AddEffect("Puff", "Puff", Vector2df(getPosition().x-14.5,getPosition().y+getHeight()-45)+m_velocity+correction);
            CAMERA->Shake();
            playSound("Pound");

            m_isJumping = false;
            m_canJump = true;
            m_jumpCounter = 0;

            handleEvent("JumpEnd");
            m_thinkTime.start(133); // 2 frames at 15 fps in milliseconds
        }
        break;
    default: // normal movement (walking, jumping, falling)
    {
        if (obj->isOnGround(getPosition())) // hit on ground
        {
            if (m_isJumping)
            {
                if (m_state == hsATTACKING)
                    m_state = hsNORMAL;
                playSound("Step");

                m_isJumping = false;

                handleEvent("JumpEnd");
                m_thinkTime.start(133); // 2 frames at 15 fps in milliseconds
            }
            m_canJump = true;
            m_jumpCounter = 0;
        }
        else // in the air (jumping or falling)
        {
            if (m_isJumping && m_velocity.y < 0) // jumping
            {
                // destory blocks hit with the head
                correction.y = data->breakTiles(obj,1,false,true).y;
            }
            if (m_jumpCounter == 0 || correction.y != 0)
                m_canJump = false;
        }
        break;
    }
    }
    if (correction.y != 0)
    {
        accelerateY(0,0);
    }
    m_velocity.y += correction.y;

    /// x-collision
    switch (m_state)
    {
    case hsATTACKING: // attacking / boxing
        // destory hit tiles
        correction.x = data->breakTiles(obj,1,true,false).x;
        if (correction.x != 0) // has he hit something hard?
        {
            // return to normal state - end boxing
            m_velocity.x = -12 * m_direction - correction.x;
            m_velocity.y = -7;
            m_state = hsNORMAL;
            m_thinkTime.start(100);
        }
        break;
    default:
        // make Wandor automatically duck when running against a narrow space
        if (correction.x != 0 && m_collisionInfo.isOnGround(getPosition()))
        {
            Vector2df tile = getPosition(true) + getPositionOffset(true);
            if (correction.x < 0)
                tile.x = ceil(tile.x + getWidth() / data->getTileSize());
            if (data->getCollisionData(tile) != 0 && data->getCollisionData(tile + Vector2df(0,getHeight() / data->getTileSize())) == 0)
            {
                duck();
                correction.x = 0;
            }
        }
        break;
    }
    if (correction.x != 0)
    {
        accelerateX(0,0);
    }
    m_velocity.x += correction.x;

    incPosition(m_velocity);

    // this is to prevent "jumping" on slants
    if (correction.y < 0)
    {
        m_velocity.y = 0;
    }
} // hitMap


void Hero::handleInput(SimpleJoy* input)
{
    if (m_state != hsCHARGING)
    {
        if(input->isA() || input->isX())
        {
            jump();
        }
        else if (m_isJumping && m_canJump)
        {
            m_velocity.y = -4;
            accelerateY(0,0);
            m_canJump = false;
        }

        if(input->isB() || input->isY())
        {
            if (isCarryingChild())
                chargeThrow();
            else
            {
                attack();
                input->resetB();
                input->resetY();
            }
        }

        if(input->isDown())
            duck();
        else
            standUp();

        if(input->isRight())
            moveRight();
        else if(input->isLeft())
            moveLeft();
        else
            noInput();

        if (m_isJumping && not m_canJump)
            input->resetA();
    }
    else // aiming the throw
    {
        if(input->isA() || input->isX())
        {
            loseChild();
            input->resetA();
            input->resetX();
            return;
        }

        if(not (input->isB() || input->isY()))
        {
            throwChild();
            input->resetB();
            input->resetY();
            return;
        }

        float targetAngle = 0.0f;
        if (input->isLeft() && input->isDown())
            targetAngle = CROSSHAIR_MIN_ANGLE;
        else if (input->isLeft() && input->isUp())
            targetAngle = 225.0f;
        else if (input->isLeft())
            targetAngle = 180.0f;
        else if (input->isRight() && input->isDown())
            targetAngle = CROSSHAIR_MAX_ANGLE;
        else if (input->isRight() && input->isUp())
            targetAngle = 315.0f;
        else if (input->isRight())
            targetAngle = 360.0f;
        else if (input->isUp())
            targetAngle = 270.0f;
        else if (input->isDown())
        {
            if (m_crosshairAngle < 270)
                targetAngle = CROSSHAIR_MIN_ANGLE;
            else if (m_crosshairAngle > 270)
                targetAngle = CROSSHAIR_MAX_ANGLE;
        }


        if (targetAngle > 0)
            m_crosshairAngle += NumberUtility::signMin(targetAngle - m_crosshairAngle,(float)CROSSHAIR_MOVEMENT_STEP);

        m_crosshairAngle = max(min(m_crosshairAngle,CROSSHAIR_MAX_ANGLE),CROSSHAIR_MIN_ANGLE);
        m_crosshair->setX(getCenter().x - m_crosshair->getWidth() / 2.0f + CROSSHAIR_DISTANCE_TO_PLAYER * cos(NumberUtility::degToRad(m_crosshairAngle)));
        m_crosshair->setY(getCenter().y - m_crosshair->getHeight() / 2.0f + CROSSHAIR_DISTANCE_TO_PLAYER * sin(NumberUtility::degToRad(m_crosshairAngle)));

        if (m_crosshairAngle < 270)
            moveLeft();
        else
            moveRight();
    }
}

void Hero::attack()
{
    if (m_state == hsNORMAL) // normal attack
    {
        m_state = hsATTACKING;
        accelerateX(12.0f * m_direction, m_maxSpeed.x * m_direction);
        m_counter.start(ATTACK_DURATION);
        if (m_direction == 1)
            setSpriteState("BoxRight");
        else
            setSpriteState("BoxLeft");
    }
}

void Hero::chargeThrow()
{
    if (m_collisionInfo.isOnGround(m_position))
    {
        m_state = hsCHARGING;
        if (not m_crosshair)
        {
            m_crosshair = new EffectCrosshair;
            EFFECTS->addCustomEffect(m_crosshair);
            if (m_direction > 0)
                m_crosshairAngle = 360;
            else
                m_crosshairAngle = 180;
            m_crosshair->setX(getCenter().x - m_crosshair->getWidth() / 2.0f + CROSSHAIR_DISTANCE_TO_PLAYER * cos(NumberUtility::degToRad(m_crosshairAngle)));
            m_crosshair->setY(getCenter().y - m_crosshair->getHeight() / 2.0f + CROSSHAIR_DISTANCE_TO_PLAYER * sin(NumberUtility::degToRad(m_crosshairAngle)));
        }
        m_velocity = Vector2df(0,0);
        accelerate(Vector2df(0,0),Vector2df(0,0));
    }
    else
    {
        throwChild();
    }
}

void Hero::throwChild()
{
    Vector2df temp(0,0);
    if (m_crosshair) // normal throw
    {
        temp = Vector2df(cos(NumberUtility::degToRad(m_crosshairAngle)),sin(NumberUtility::degToRad(m_crosshairAngle))) * MAX_THROW_FORCE;
        m_crosshair->isFinished();
        m_crosshair = NULL;

        if (m_direction < 0)
            setSpriteState("ThrowStandLeft");
        else
            setSpriteState("ThrowStandRight");
    }
    else // air throw
    {
        temp = Vector2df(m_direction * MAX_THROW_FORCE * 0.75,0);

        if (m_direction < 0)
            setSpriteState("ThrowJumpLeft");
        else
            setSpriteState("ThrowJumpRight");
    }

    m_child->m_velocity = temp;

    m_child->setState(usPROJECTILE);
    resetChild();

    m_state = hsNORMAL;
    m_thinkTime.start(250);
}

void Hero::loseChild()
{
    if (m_crosshair)
    {
        m_crosshair->isFinished();
        m_crosshair = NULL;
    }

    m_child->m_velocity = Vector2df(0,0);
    resetChild();

    m_state = hsNORMAL;
    m_thinkTime.start(250);
}

void Hero::jump()
{
    if (m_canJump)
    {
        if (m_jumpCounter == 0)
            playSound("Jump");

        switch (m_state)
        {
        case hsDUCKED:
            accelerateY(MAX_JUMP_SPEED * 0.4,MAX_JUMP_SPEED * 0.7);
            break;
        case hsNORMAL:
            accelerateY(MAX_JUMP_SPEED * 0.7,MAX_JUMP_SPEED);
            if (m_direction < 0)
            {
                if (isCarryingChild())
                    setSpriteState("ThrowJumpLeft");
                else
                    setSpriteState("JumpLeft");
            }
            else
            {
                if (isCarryingChild())
                    setSpriteState("JumpRight");
                else
                    setSpriteState("ThrowJumpLeft");
            }
            break;
        case hsCHARGING:
            break;
        default:
            accelerateY(MAX_JUMP_SPEED * 0.7,MAX_JUMP_SPEED);
            break;
        }
        m_isJumping = true;
        if (m_jumpCounter >= MAX_JUMP_COUNTER)
        {
            m_canJump = false;
            m_velocity.y = -8;
            accelerateY(0,0);
        }
        m_jumpCounter++;
    }
} // jump

void Hero::moveLeft()
{
    switch (m_state)
    {
    case hsATTACKING:
        // stop attacking if looking in opposite direction
        if (m_direction != -1)
        {
            m_state = hsNORMAL;
            m_velocity.x = 0;
            m_counter.stop();
            setSpriteState("Left");
        }
        else
        {
            accelerateX(12.0 * m_direction,m_maxSpeed.x * m_direction);
        }
        break;
    case hsPOUNDING:
        accelerateX(max(m_acceleration[0].x - 1,-3.0f),MAX_NORMAL_MOVEMENT * -0.5);
        break;
    case hsDUCKED:
        accelerateX(min(m_acceleration[0].x - 2,-6.0f),MAX_NORMAL_MOVEMENT * -0.5);
        setSpriteState("CrouchLeft");
        break;
    case hsCHARGING:
        setSpriteState("GrabStandLeft");
        break;
    default:
        if (m_direction != -1 && not isCarryingChild()) // looking in opposite direction
        {
            // play turn animation
            if (m_collisionInfo.isOnGround(m_position))
            {
                setSpriteState("Turn");
                m_sprite->setReversePlay(false);
                m_sprite->setCurrentFrame(0);
                m_thinkTime.start(276); // 4 frames, 15 fps, in milliseconds(1000/15*4) = 267
            }
        }
        else
        {
            accelerateX(max(m_acceleration[0].x - 2,-6.0f),MAX_NORMAL_MOVEMENT * -1);
            if (not m_isJumping)
            {
                if (isCarryingChild())
                    setSpriteState("GrabWalkLeft");
                else
                    setSpriteState("WalkingLeft");
            }
            else
            {
                if (isCarryingChild())
                    setSpriteState("ThrowJumpLeft");
                else
                    setSpriteState("JumpLeft");
            }
        }
        break;
    }
    m_direction = -1;
}

void Hero::moveRight()
{
    switch (m_state)
    {
    case hsATTACKING:
        // stop attacking if looking in opposite direction
        if (m_direction != 1)
        {
            m_state = hsNORMAL;
            m_velocity.x = 0;
            m_counter.stop();
            setSpriteState("Right");
        }
        else
        {
            accelerateX(12.0 * m_direction,m_maxSpeed.x * m_direction);
        }
        break;
    case hsPOUNDING:
        accelerateX(min(m_acceleration[0].x + 1,3.0f),MAX_NORMAL_MOVEMENT * 0.5);
        break;
    case hsDUCKED:
        accelerateX(min(m_acceleration[0].x + 2,6.0f),MAX_NORMAL_MOVEMENT * 0.5);
        setSpriteState("CrouchRight");
        break;
    case hsCHARGING:
        setSpriteState("GrabStandRight");
        break;
    default:
        if (m_direction != 1 && not isCarryingChild()) // looking in opposite direction
        {
            // play turn animation
            if (m_collisionInfo.isOnGround(m_position))
            {
                setSpriteState("Turn");
                m_sprite->setReversePlay(true);
                m_sprite->setCurrentFrame(3);
                m_thinkTime.start(267); // 4 frames, 15 fps, in milliseconds (1000/15*4) = 267
            }
        }
        else
        {
            accelerateX(min(m_acceleration[0].x + 2,6.0f),MAX_NORMAL_MOVEMENT);
            if (not m_isJumping)
            {
                if (isCarryingChild())
                    setSpriteState("GrabWalkRight");
                else
                    setSpriteState("WalkingRight");
            }
            else
            {
                if (isCarryingChild())
                    setSpriteState("ThrowJumpRight");
                else
                    setSpriteState("JumpRight");
            }
        }
        break;
    }
    m_direction = 1;
}

void Hero::duck()
{
    if (m_state == hsNORMAL)
    {
        if (isCarryingChild())
            loseChild();

        if (m_collisionInfo.isInAir(getPosition()))
        {
            m_state = hsPOUNDING;
            if (m_direction < 0)
                setSpriteState("StompLeft");
            else
                setSpriteState("StompRight");
            m_velocity.y = MAX_POUND_SPEED;
            m_velocity.x = 0;
            accelerateY(0,0);
            m_canJump = false;
        }
        else
        {
            m_state = hsDUCKED;
            if (m_direction < 0)
                setSpriteState("DuckLeft");
            else
                setSpriteState("DuckRight");
        }
    }
}

void Hero::standUp()
{
    if (m_state == hsDUCKED)
    {
        if (getLevel()->tileMap->getCollisionData(getPosition(true) + getPositionOffset(true)) == 0 &&
            getLevel()->tileMap->getCollisionData(getPosition(true) + getPositionOffset(true) + Vector2df(getWidth() / (float)getLevel()->GetTileSize(),0)) == 0)
            m_state = hsNORMAL;
    }
}

void Hero::noInput()
{
    switch (m_state)
    {
    case hsATTACKING:
        // continnue attacking
        accelerateX(12.0 * m_direction,m_maxSpeed.x * m_direction);
        break;
    case hsPOUNDING:
        // remove x-movement for an unrealistic, but more fun arcade-style movement
        m_velocity.x = 0;
        accelerateX(0,0);
        break;
    case hsDUCKED:
        accelerateX(0,0);
        if (m_direction < 0)
            setSpriteState("DuckLeft");
        else
            setSpriteState("DuckRight");
        if (m_isJumping)
            m_velocity.x = 0;
        break;
    case hsCHARGING:
        break;
    default:
        if (m_velocity.x == 0 && not m_isJumping)
        {
            if (m_direction == 1)
            {
                if (isCarryingChild())
                    setSpriteState("GrabStandRight");
                else
                    setSpriteState("Right");
            }
            else
            {
                if (isCarryingChild())
                    setSpriteState("GrabStandLeft");
                else
                    setSpriteState("Left");
            }
        }
        else if (m_isJumping)
        {
            if (m_direction == 1)
            {
                if (isCarryingChild())
                    setSpriteState("ThrowJumpRight");
                else
                    setSpriteState("JumpRight");
            }
            else
            {
                if (isCarryingChild())
                    setSpriteState("ThrowJumpLeft");
                else
                    setSpriteState("JumpLeft");
            }
            // remove x-movement for an unrealistic, but more fun arcade-style movement
            m_velocity.x = 0;
        }
        accelerateX(0,0);
        break;
    }
}

void Hero::customUpdate()
{
    switch (m_state)
    {
    case hsATTACKING:
        if (not m_isJumping)
            m_state = hsNORMAL;
        break;
    default:
        break;
    }
}
